/*
 * Copyright (c) 2012 Johan Fylling
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 */

part of engine;


class RectangleParticleSystem extends ParticleSystem implements ParticleTouchListener {
  num                _mass;
  num                massInv;
  Particle           _p1;
  Particle           _p2;
  Particle           _p3;
  Particle           _p4;
  DistanceConstraint _p1p2;
  DistanceConstraint _p2p3;
  DistanceConstraint _p3p4;
  DistanceConstraint _p4p1;
  DistanceConstraint _p1p3;
  DistanceConstraint _p2p4;
  num                _baseAngle;
  Vector2            _halfSize;
  
  bool               _touchedPosition = true;
  bool               _touchedAngle    = true;
  bool               _touchedSize     = true;
  final Vector2      _position        = new Vector2();
  num                _angle           = 0.0;
  
  RectangleParticleSystem (num mass, Vector2 halfSize, [Vector2 position, num angle=0.0]) {
    this.mass = mass;
    num particleMass = mass / 4;
    this._halfSize = new Vector2.copy(halfSize);
    
    this._p1 = new Particle(particleMass, halfSize, this);
    addParticle(this._p1);
    this._p2 = new Particle(particleMass, new Vector2(halfSize.x, -halfSize.y), this);
    addParticle(this._p2);
    this._p3 = new Particle(particleMass, new Vector2(-halfSize.x, -halfSize.y), this);
    addParticle(this._p3);
    this._p4 = new Particle(particleMass, new Vector2(-halfSize.x, halfSize.y), this);
    addParticle(this._p4);
    
    this._p1p2 = new DistanceConstraint(this._p1, this._p2);
    addConstraint(this._p1p2);
    this._p2p3 = new DistanceConstraint(this._p2, this._p3);
    addConstraint(this._p2p3);
    this._p3p4 = new DistanceConstraint(this._p3, this._p4);
    addConstraint(this._p3p4);
    this._p4p1 = new DistanceConstraint(this._p4, this._p1);
    addConstraint(this._p4p1);
    this._p1p3 = new DistanceConstraint(this._p1, this._p3);
    addConstraint(this._p1p3);
    this._p2p4 = new DistanceConstraint(this._p2, this._p4);
    addConstraint(this._p2p4);
    
    this._baseAngle = DynamicBody.baseOrientation.angle(halfSize);
  }
  
  void set halfSize (Vector2 halfSize) {
    this._halfSize = halfSize;
    num angle = this.angle;
    Vector2 position = this.position;
    
    Vector2 diff = this._p1.positionPrev - this._p1.position;
    this._p1.position = position + halfSize;
    this._p1.positionPrev = this._p1.position + diff;
    
    diff = this._p2.positionPrev - this._p2.position;
    this._p2.position = position + new Vector2(halfSize.x, -halfSize.y);
    this._p2.positionPrev = this._p2.position + diff;
    
    diff = this._p3.positionPrev - this._p3.position;
    this._p3.position = position + new Vector2(-halfSize.x, -halfSize.y);
    this._p3.positionPrev = this._p3.position + diff;
    
    diff = this._p4.positionPrev - this._p4.position;
    this._p4.position = position + new Vector2(-halfSize.x, halfSize.y);
    this._p4.positionPrev = this._p4.position + diff;
    
    this._p1p2.fix();
    this._p2p3.fix();
    this._p3p4.fix();
    this._p4p1.fix();
    this._p1p3.fix();
    this._p2p4.fix();
    
    this._baseAngle = DynamicBody.baseOrientation.angle(halfSize);
    this.angle = angle;
  }
  
  num get mass => this._mass;
  
  void set mass (num m) {
    this._mass = m;
    this.massInv = (m != 0 ? 1 / m : 0);
  }
  
  Vector2 get position {
    if (this._touchedPosition) {
      this._touchedPosition = false;
      this._position.clone((this._p1.position + 
                            this._p2.position + 
                            this._p3.position + 
                            this._p4.position) / 4);
    }
    return new Vector2.copy(this._position);
  }
  
  void set position (Vector2 position) {
    Vector2 diff = position - this.position;
    
    this._p1.position = this._p1.position + diff;
    this._p1.positionPrev = this._p1.position;
    
    this._p2.position = this._p2.position + diff;
    this._p2.positionPrev = this._p2.position;
    
    this._p3.position = this._p3.position + diff;
    this._p3.positionPrev = this._p3.position;
    
    this._p4.position = this._p4.position + diff;
    this._p4.positionPrev = this._p4.position;
  }
  
  Vector2 get positionPrev {
    return (this._p1.positionPrev + 
            this._p2.positionPrev + 
            this._p3.positionPrev + 
            this._p4.positionPrev) / 4;
  }
  
  num get angle {
    if (this._touchedAngle) {
      this._touchedAngle = false;
      this._angle = DynamicBody.baseOrientation.angle(this._p1.position - this.position) - this._baseAngle;
    }
    return this._angle;
  }
  
  void set angle (num angle) {
    Vector2 currentPosition = this.position;
    num halfLength = this._halfSize.length;
    
    Vector2 orientation = new Vector2.fromAngle(angle, new Vector2(this._halfSize.x, this._halfSize.y));
    this._p1.position = orientation.normalized()*halfLength + currentPosition;
    this._p1.positionPrev = this._p1.position;
    
    orientation.clone(new Vector2.fromAngle(angle, new Vector2(this._halfSize.x, -this._halfSize.y)));
    this._p2.position = orientation.normalized()*halfLength + currentPosition;
    this._p2.positionPrev = this._p2.position;
    
    orientation.clone(new Vector2.fromAngle(angle, new Vector2(-this._halfSize.x, -this._halfSize.y)));
    this._p3.position = orientation.normalized()*halfLength + currentPosition;
    this._p3.positionPrev = this._p3.position;
    
    orientation.clone(new Vector2.fromAngle(angle, new Vector2(-this._halfSize.x, this._halfSize.y)));
    this._p4.position = orientation.normalized()*halfLength + currentPosition;
    this._p4.positionPrev = this._p4.position;
  }
  
  Vector2 get velocity => this.position - this.positionPrev;
  
  void set velocity (Vector2 velocity) {
    this._p1.positionPrev = this._p1.position - velocity;
    this._p2.positionPrev = this._p2.position - velocity;
    this._p3.positionPrev = this._p3.position - velocity;
    this._p4.positionPrev = this._p4.position - velocity;
  }
  
  bool get touched => this._touchedPosition || this._touchedAngle;
  
  void particleTouched (Particle p) {
    this._touchedPosition = this._touchedAngle = true;
  }
}
